<?php
/**
 * Created by PhpStorm
 * User: 龍zero
 * Date: 2022-04-14
 * Time: 10:00
 */

namespace App\Aspect;


use App\Annotation\Logger;
use App\Annotation\PermissionMeta;
use App\Lib\Tools\LocalUser;
use App\Service\LogService;
use Closure;
use FastRoute\Dispatcher;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\HttpServer\Router\Dispatched;
use Hyperf\Server\Exception\ServerException;

/**
 * @Aspect()
 * Class LoggerAspect
 * @package App\Aspect
 */
class LoggerAspect extends AbstractAspect
{
    /**
     * @Inject()
     * @var RequestInterface
     */
    private $request;

    /**
     * @Inject()
     * @var ResponseInterface
     */
    private $response;

    /**
     * @Inject()
     * @var LogService
     */
    private $logService;

    public array $annotations = [
        Logger::class
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $res = $proceedingJoinPoint->process();
        $loggerMeta = $proceedingJoinPoint->getAnnotationMetadata()->method[Logger::class]->toArray();
        $user = LocalUser::getLocalUser();
        if($loggerMeta && $user && $template = $loggerMeta['template']){
            $pattern = '/(?<=\{)[^}]*(?=})/';
            preg_match_all($pattern,$template,$match);
            foreach ($match[0] as $matchItem){
                $reStr = $this->extractProperty($matchItem,$user,$res);
                $template = str_replace('{'.$matchItem.'}',$reStr,$template);
            }
            $permissionMeta = $proceedingJoinPoint->getAnnotationMetadata()->method[PermissionMeta::class] ?? null;
            if($permissionMeta){
                $permissionMeta = $permissionMeta->toArray();
            }
            $path = $this->request->getRequestUri();
            $method = $this->request->getMethod();
            $userId = $user->id;
            $username = $user->username;
            $permission = $permissionMeta['value'] ?? '';
            $status = $this->response->getStatusCode();
            $this->logService->createLog($template,$permission,$userId,$username,$method,$path,$status);
        }

        return $res;
    }

    protected function extractProperty($item,$user,$res){
        $matchArr = explode('.',$item);
        if(count($matchArr) >= 2){
            $obj = $matchArr[0];
            $prop = $matchArr[1];
            $attr = $matchArr[2]??'';
            switch ($obj) {
                case "user":
                    return $user->$prop ?? "";
                case "request":
                    if($prop == 'body'){
                        return json_encode($this->request->getParsedBody());
                    }elseif ($prop == 'header'){
                        $header = [
                            'content-type' => $this->request->getHeader('content-type'),
                            'host' =>  $this->request->getHeader('host'),
                            'content-length' => $this->request->getHeader('content-length'),
                        ];
                        return json_encode($header);
                    }elseif ($prop == 'query'){
                        return $this->request->getQueryString();
                    }elseif($prop == 'param'){
                        $dispatched = $this->request->getAttribute(Dispatched::class);
                        if (!$dispatched instanceof Dispatched) {
                            throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
                        }
                        if($this->shouldHandle($dispatched)){
                            $params = $dispatched->params;
                            return $attr ? $params[$attr] ?? '': '';
                        }
                    }else{
                        return '';
                    }
                case "response":
                    if($prop == 'body'){
                        return $res->getBody()->getContents();
                    }elseif ($prop == 'header'){
                        return  json_encode($res->getHeader('content-type'));
                    }elseif ($prop == 'charset'){
                        return $res->getCharset();
                    }else{
                        return '';
                    }
                default:
                    return "";
            }
        }
    }

    protected function shouldHandle(Dispatched $dispatched): bool
    {
        return $dispatched->status === Dispatcher::FOUND && ! $dispatched->handler->callback instanceof Closure;
    }
}